/*
 * FreeRTOS+TCP V2.0.3
 * Copyright (C) 2017 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */


/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "FreeRTOS_IP_Private.h"
#include "NetworkBufferManagement.h"
#include "NetworkInterface.h"

/* LPCOpen includes. */
#include "chip.h"
#include "lpc_phy.h"

/* The size of the stack allocated to the task that handles Rx packets. */
#define nwRX_TASK_STACK_SIZE	140

#ifndef	PHY_LS_HIGH_CHECK_TIME_MS
	/* Check if the LinkSStatus in the PHY is still high after 15 seconds of not
	receiving packets. */
	#define PHY_LS_HIGH_CHECK_TIME_MS	15000
#endif

#ifndef	PHY_LS_LOW_CHECK_TIME_MS
	/* Check if the LinkSStatus in the PHY is still low every second. */
	#define PHY_LS_LOW_CHECK_TIME_MS	1000
#endif

#ifndef configUSE_RMII
	#define configUSE_RMII 1
#endif

#ifndef configNUM_RX_DESCRIPTORS
	#error please define configNUM_RX_DESCRIPTORS in your FreeRTOSIPConfig.h
#endif

#ifndef configNUM_TX_DESCRIPTORS
	#error please define configNUM_TX_DESCRIPTORS in your FreeRTOSIPConfig.h
#endif

#ifndef NETWORK_IRQHandler
	#error NETWORK_IRQHandler must be defined to the name of the function that is installed in the interrupt vector table to handle Ethernet interrupts.
#endif

#if !defined( MAC_FF_HMC )
	/* Hash for multicast. */
	#define MAC_FF_HMC     ( 1UL << 2UL )
#endif

#ifndef iptraceEMAC_TASK_STARTING
	#define iptraceEMAC_TASK_STARTING()	do { } while( 0 )
#endif

/* Define the bits of .STATUS that indicate a reception error. */
#define nwRX_STATUS_ERROR_BITS \
	( RDES_CE  /* CRC Error */                        | \
	  RDES_RE  /* Receive Error */                    | \
	  RDES_DE  /* Descriptor Error */                 | \
	  RDES_RWT /* Receive Watchdog Timeout */         | \
	  RDES_LC  /* Late Collision */                   | \
	  RDES_OE  /* Overflow Error */                   | \
	  RDES_SAF /* Source Address Filter Fail */       | \
	  RDES_AFM /* Destination Address Filter Fail */  | \
	  RDES_LE  /* Length Error */                     )

/* Define the EMAC status bits that should trigger an interrupt. */
#define nwDMA_INTERRUPT_MASK \
	( DMA_IE_TIE  /* Transmit interrupt enable */         | \
	  DMA_IE_TSE  /* Transmit stopped enable */           | \
	  DMA_IE_OVE  /* Overflow interrupt enable */         | \
	  DMA_IE_RIE  /* Receive interrupt enable */          | \
	  DMA_IE_NIE  /* Normal interrupt summary enable */   | \
	  DMA_IE_AIE  /* Abnormal interrupt summary enable */ | \
	  DMA_IE_RUE  /* Receive buffer unavailable enable */ | \
	  DMA_IE_UNE  /* Underflow interrupt enable. */       | \
	  DMA_IE_TJE  /* Transmit jabber timeout enable */    | \
	  DMA_IE_RSE  /* Received stopped enable */           | \
	  DMA_IE_RWE  /* Receive watchdog timeout enable */   | \
	  DMA_IE_FBE )/* Fatal bus error enable */

/* Interrupt events to process.  Currently only the RX/TX events are processed
although code for other events is included to allow for possible future
expansion. */
#define EMAC_IF_RX_EVENT        1UL
#define EMAC_IF_TX_EVENT        2UL
#define EMAC_IF_ERR_EVENT       4UL
#define EMAC_IF_ALL_EVENT       ( EMAC_IF_RX_EVENT | EMAC_IF_TX_EVENT | EMAC_IF_ERR_EVENT )

 /* If ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES is set to 1, then the Ethernet
 driver will filter incoming packets and only pass the stack those packets it
 considers need processing. */
 #if( ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES == 0 )
 	#define ipCONSIDER_FRAME_FOR_PROCESSING( pucEthernetBuffer ) eProcessBuffer
 #else
 	#define ipCONSIDER_FRAME_FOR_PROCESSING( pucEthernetBuffer ) eConsiderFrameForProcessing( ( pucEthernetBuffer ) )
 #endif

#if( ipconfigZERO_COPY_RX_DRIVER == 0 ) || ( ipconfigZERO_COPY_TX_DRIVER == 0 )
	#warning It is adviced to enable both macros
#endif

#ifndef configPLACE_IN_SECTION_RAM
	#define configPLACE_IN_SECTION_RAM
/*
	#define configPLACE_IN_SECTION_RAM	__attribute__ ((section(".ramfunc")))
*/
#endif

/*-----------------------------------------------------------*/

/*
 * Delay function passed into the library.  The implementation uses FreeRTOS
 * calls so the scheduler must be started before the driver can be used.
 */
static void prvDelay( uint32_t ulMilliSeconds );

/*
 * Initialises the Tx and Rx descriptors respectively.
 */
static void prvSetupTxDescriptors( void );
static void prvSetupRxDescriptors( void );

/*
 * A task that processes received frames.
 */
static void prvEMACHandlerTask( void *pvParameters );

/*
 * Sets up the MAC with the results of an auto-negotiation.
 */
static BaseType_t prvSetLinkSpeed( void );

/*
 * Generates a CRC for a MAC address that is then used to generate a hash index.
 */
static uint32_t prvGenerateCRC32( const uint8_t *ucAddress );

/*
 * Generates a hash index when setting a filter to permit a MAC address.
 */
static uint32_t prvGetHashIndex( const uint8_t *ucAddress );

/*
 * Update the hash table to allow a MAC address.
 */
static void prvAddMACAddress( const uint8_t* ucMacAddress );

/*
 * Sometimes the DMA will report received data as being longer than the actual
 * received from length.  This function checks the reported length and corrects
 * if if necessary.
 */
static void prvRemoveTrailingBytes( NetworkBufferDescriptor_t *pxDescriptor );

/*-----------------------------------------------------------*/

/* Bit map of outstanding ETH interrupt events for processing.  Currently only
the Rx and Tx interrupt is handled, although code is included for other events
to enable future expansion. */
static volatile uint32_t ulISREvents;

/* A copy of PHY register 1: 'PHY_REG_01_BMSR' */
static uint32_t ulPHYLinkStatus = 0;

/* Tx descriptors and index. */
static ENET_ENHTXDESC_T xDMATxDescriptors[ configNUM_TX_DESCRIPTORS ];

/* ulNextFreeTxDescriptor is declared volatile, because it is accessed from
to different tasks. */
static volatile uint32_t ulNextFreeTxDescriptor;
static uint32_t ulTxDescriptorToClear;

/* Rx descriptors and index. */
static ENET_ENHRXDESC_T xDMARxDescriptors[ configNUM_RX_DESCRIPTORS ];
static uint32_t ulNextRxDescriptorToProcess;

/* Must be defined externally - the demo applications define this in main.c. */
extern uint8_t ucMACAddress[ 6 ];

/* The handle of the task that processes Rx packets.  The handle is required so
the task can be notified when new packets arrive. */
static TaskHandle_t xRxHanderTask = NULL;

#if( ipconfigUSE_LLMNR == 1 )
	static const uint8_t xLLMNR_MACAddress[] = { '\x01', '\x00', '\x5E', '\x00', '\x00', '\xFC' };
#endif	/* ipconfigUSE_LLMNR == 1 */

/* xTXDescriptorSemaphore is a counting semaphore with
a maximum count of ETH_TXBUFNB, which is the number of
DMA TX descriptors. */
static SemaphoreHandle_t xTXDescriptorSemaphore = NULL;

/*-----------------------------------------------------------*/


BaseType_t xNetworkInterfaceInitialise( void )
{
BaseType_t xReturn = pdPASS;
static BaseType_t xHasInitialised = pdFALSE;

	if( xHasInitialised == pdFALSE )
	{
		xHasInitialised = pdTRUE;

		/* The interrupt will be turned on when a link is established. */
		NVIC_DisableIRQ( ETHERNET_IRQn );

		/* Disable receive and transmit DMA processes. */
		LPC_ETHERNET->DMA_OP_MODE &= ~( DMA_OM_ST | DMA_OM_SR );

		/* Disable packet reception. */
		LPC_ETHERNET->MAC_CONFIG &= ~( MAC_CFG_RE | MAC_CFG_TE );

		/* Call the LPCOpen function to initialise the hardware. */
		Chip_ENET_Init( LPC_ETHERNET );

		/* Save MAC address. */
		Chip_ENET_SetADDR( LPC_ETHERNET, ucMACAddress );

		/* Clear all MAC address hash entries. */
		LPC_ETHERNET->MAC_HASHTABLE_HIGH = 0;
		LPC_ETHERNET->MAC_HASHTABLE_LOW = 0;

		#if( ipconfigUSE_LLMNR == 1 )
		{
			prvAddMACAddress( xLLMNR_MACAddress );
		}
		#endif /* ipconfigUSE_LLMNR == 1 */

		/* Promiscuous flag (PR) and Receive All flag (RA) set to zero.  The
		registers MAC_HASHTABLE_[LOW|HIGH] will be loaded to allow certain
		multi-cast addresses. */
		LPC_ETHERNET->MAC_FRAME_FILTER = MAC_FF_HMC;

		#if( configUSE_RMII == 1 )
		{
			if( lpc_phy_init( pdTRUE, prvDelay ) != SUCCESS )
			{
				xReturn = pdFAIL;
			}
		}
		#else
		{
			#warning This path has not been tested.
			if( lpc_phy_init( pdFALSE, prvDelay ) != SUCCESS )
			{
				xReturn = pdFAIL;
			}
		}
		#endif

		if( xReturn == pdPASS )
		{
			if( xTXDescriptorSemaphore == NULL )
			{
				/* Create a counting semaphore, with a value of 'configNUM_TX_DESCRIPTORS'
				and a maximum of 'configNUM_TX_DESCRIPTORS'. */
				xTXDescriptorSemaphore = xSemaphoreCreateCounting( ( UBaseType_t ) configNUM_TX_DESCRIPTORS, ( UBaseType_t ) configNUM_TX_DESCRIPTORS );
				configASSERT( xTXDescriptorSemaphore );
			}

			/* Enable MAC interrupts. */
			LPC_ETHERNET->DMA_INT_EN = nwDMA_INTERRUPT_MASK;

			/* Auto-negotiate was already started.  Wait for it to complete. */
			xReturn = prvSetLinkSpeed();

			if( xReturn == pdPASS )
			{
				/* Initialise the descriptors. */
				prvSetupTxDescriptors();
				prvSetupRxDescriptors();

				/* Clear all interrupts. */
				LPC_ETHERNET->DMA_STAT = DMA_ST_ALL;

				/* Enable receive and transmit DMA processes. */
				LPC_ETHERNET->DMA_OP_MODE |= DMA_OM_ST | DMA_OM_SR;

				/* Set Receiver / Transmitter Enable. */
				LPC_ETHERNET->MAC_CONFIG |= MAC_CFG_RE | MAC_CFG_TE;

				/* Start receive polling. */
				LPC_ETHERNET->DMA_REC_POLL_DEMAND = 1;

				/* Enable interrupts in the NVIC. */
				NVIC_SetPriority( ETHERNET_IRQn, configMAC_INTERRUPT_PRIORITY );
				NVIC_EnableIRQ( ETHERNET_IRQn );
			}
			/* Guard against the task being created more than once and the
			descriptors being initialised more than once. */
			if( xRxHanderTask == NULL )
			{
				xReturn = xTaskCreate( prvEMACHandlerTask, "EMAC", nwRX_TASK_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, &xRxHanderTask );
				configASSERT( xReturn );
			}
		}
	}

	/* Once prvEMACHandlerTask() has started, the variable
	'ulPHYLinkStatus' will be updated by that task. 
	The IP-task will keep on calling this function untill
	it finally returns pdPASS.
	Only then can the DHCP-procedure start (if configured). */
	if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != 0 )
	{
		xReturn = pdPASS;
	}
	else
	{
		xReturn = pdFAIL;
	}

	return xReturn;
}
/*-----------------------------------------------------------*/

#define niBUFFER_1_PACKET_SIZE		1536

static __attribute__ ((section("._ramAHB32"))) uint8_t ucNetworkPackets[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS * niBUFFER_1_PACKET_SIZE ] __attribute__ ( ( aligned( 32 ) ) );

void vNetworkInterfaceAllocateRAMToBuffers( NetworkBufferDescriptor_t pxNetworkBuffers[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS ] )
{

uint8_t *ucRAMBuffer = ucNetworkPackets;
uint32_t ul;

	for( ul = 0; ul < ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS; ul++ )
	{
		pxNetworkBuffers[ ul ].pucEthernetBuffer = ucRAMBuffer + ipBUFFER_PADDING;
		*( ( unsigned * ) ucRAMBuffer ) = ( unsigned ) ( &( pxNetworkBuffers[ ul ] ) );
		ucRAMBuffer += niBUFFER_1_PACKET_SIZE;
	}
}
/*-----------------------------------------------------------*/

configPLACE_IN_SECTION_RAM
static void vClearTXBuffers()
{
uint32_t ulLastDescriptor = ulNextFreeTxDescriptor;
size_t uxCount = ( ( size_t ) configNUM_TX_DESCRIPTORS ) - uxSemaphoreGetCount( xTXDescriptorSemaphore );
#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
	NetworkBufferDescriptor_t *pxNetworkBuffer;
	uint8_t *ucPayLoad;
#endif

	/* This function is called after a TX-completion interrupt.
	It will release each Network Buffer used in xNetworkInterfaceOutput().
	'uxCount' represents the number of descriptors given to DMA for transmission.
	After sending a packet, the DMA will clear the 'TDES_OWN' bit. */
	while( ( uxCount > ( size_t ) 0u ) && ( ( xDMATxDescriptors[ ulTxDescriptorToClear ].CTRLSTAT & TDES_OWN ) == 0 ) )
	{
		if( ( ulTxDescriptorToClear == ulLastDescriptor ) && ( uxCount != ( size_t ) configNUM_TX_DESCRIPTORS ) )
		{
			break;
		}


		#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
		{
			ucPayLoad = ( uint8_t * )xDMATxDescriptors[ ulTxDescriptorToClear ].B1ADD;
			if( ucPayLoad != NULL )
			{
				/* B1ADD points to a pucEthernetBuffer of a Network Buffer descriptor. */
				pxNetworkBuffer = pxPacketBuffer_to_NetworkBuffer( ucPayLoad );

				configASSERT( pxNetworkBuffer != NULL );

				vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer ) ;
				xDMATxDescriptors[ ulTxDescriptorToClear ].B1ADD = ( uint32_t )0u;
			}
		}
		#endif /* ipconfigZERO_COPY_TX_DRIVER */

		/* Move onto the next descriptor, wrapping if necessary. */
		ulTxDescriptorToClear++;
		if( ulTxDescriptorToClear >= configNUM_TX_DESCRIPTORS )
		{
			ulTxDescriptorToClear = 0;
		}

		uxCount--;
		/* Tell the counting semaphore that one more TX descriptor is available. */
		xSemaphoreGive( xTXDescriptorSemaphore );
	}
}

/*-----------------------------------------------------------*/

configPLACE_IN_SECTION_RAM
BaseType_t xNetworkInterfaceOutput( NetworkBufferDescriptor_t * const pxDescriptor, BaseType_t bReleaseAfterSend )
{
BaseType_t xReturn = pdFAIL;
const TickType_t xBlockTimeTicks = pdMS_TO_TICKS( 50 );

	/* Attempt to obtain access to a Tx descriptor. */
	do
	{
		if( xTXDescriptorSemaphore == NULL )
		{
			break;
		}
		if( xSemaphoreTake( xTXDescriptorSemaphore, xBlockTimeTicks ) != pdPASS )
		{
			/* Time-out waiting for a free TX descriptor. */
			break;
		}

		/* If the descriptor is still owned by the DMA it can't be used. */
		if( ( xDMATxDescriptors[ ulNextFreeTxDescriptor ].CTRLSTAT & TDES_OWN ) != 0 )
		{
			/* The semaphore was taken, the TX DMA-descriptor is still not available.
			Actually that should not occur, the 'TDES_OWN' was already confirmed low in vClearTXBuffers(). */
			xSemaphoreGive( xTXDescriptorSemaphore );
		}
		else
		{
			#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
			{
				/* bReleaseAfterSend should always be set when using the zero
				copy driver. */
				configASSERT( bReleaseAfterSend != pdFALSE );

				/* The DMA's descriptor to point directly to the data in the
				network buffer descriptor.  The data is not copied. */
				xDMATxDescriptors[ ulNextFreeTxDescriptor ].B1ADD = ( uint32_t ) pxDescriptor->pucEthernetBuffer;

				/* The DMA descriptor will 'own' this Network Buffer,
				until it has been sent.  So don't release it now. */
				bReleaseAfterSend = pdFALSE;
			}
			#else
			{
				/* The data is copied from the network buffer descriptor into
				the DMA's descriptor. */
				memcpy( ( void * ) xDMATxDescriptors[ ulNextFreeTxDescriptor ].B1ADD, ( void * ) pxDescriptor->pucEthernetBuffer, pxDescriptor->xDataLength );
			}
			#endif

			xDMATxDescriptors[ ulNextFreeTxDescriptor ].BSIZE = ( uint32_t ) TDES_ENH_BS1( pxDescriptor->xDataLength );

			/* This descriptor is given back to the DMA. */
			xDMATxDescriptors[ ulNextFreeTxDescriptor ].CTRLSTAT |= TDES_OWN;

			/* Ensure the DMA is polling Tx descriptors. */
			LPC_ETHERNET->DMA_TRANS_POLL_DEMAND = 1;

            iptraceNETWORK_INTERFACE_TRANSMIT();

			/* Move onto the next descriptor, wrapping if necessary. */
			ulNextFreeTxDescriptor++;
			if( ulNextFreeTxDescriptor >= configNUM_TX_DESCRIPTORS )
			{
				ulNextFreeTxDescriptor = 0;
			}

			/* The Tx has been initiated. */
			xReturn = pdPASS;
		}
	} while( 0 );

	/* The buffer has been sent so can be released. */
	if( bReleaseAfterSend != pdFALSE )
	{
		vReleaseNetworkBufferAndDescriptor( pxDescriptor );
	}

	return xReturn;
}
/*-----------------------------------------------------------*/

static void prvDelay( uint32_t ulMilliSeconds )
{
	/* Ensure the scheduler was started before attempting to use the scheduler to
	create a delay. */
	configASSERT( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING );

	vTaskDelay( pdMS_TO_TICKS( ulMilliSeconds ) );
}
/*-----------------------------------------------------------*/

static void prvSetupTxDescriptors( void )
{
BaseType_t x;

	/* Start with Tx descriptors clear. */
	memset( ( void * ) xDMATxDescriptors, 0, sizeof( xDMATxDescriptors ) );

	/* Index to the next Tx descriptor to use. */
	ulNextFreeTxDescriptor = 0ul;

	/* Index to the next Tx descriptor to clear ( after transmission ). */
	ulTxDescriptorToClear = 0ul;

	for( x = 0; x < configNUM_TX_DESCRIPTORS; x++ )
	{
		#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
		{
			/* Nothing to do, B1ADD will be set when data is ready to transmit.
			Currently the memset above will have set it to NULL. */
		}
		#else
		{
			/* Allocate a buffer to the Tx descriptor.  This is the most basic
			way of creating a driver as the data is then copied into the
			buffer. */
			xDMATxDescriptors[ x ].B1ADD = ( uint32_t ) pvPortMalloc( ipTOTAL_ETHERNET_FRAME_SIZE );

			/* Use an assert to check the allocation as +TCP applications will
			often not use a malloc() failed hook as the TCP stack will recover
			from allocation failures. */
			configASSERT( xDMATxDescriptors[ x ].B1ADD );
		}
		#endif

		/* Buffers hold an entire frame so all buffers are both the start and
		end of a frame. */
		/* TDES_ENH_TCH     Second Address Chained. */
		/* TDES_ENH_CIC(n)  Checksum Insertion Control, tried but it does not work for the LPC18xx... */
		/* TDES_ENH_FS      First Segment. */
		/* TDES_ENH_LS      Last Segment. */
		/* TDES_ENH_IC      Interrupt on Completion. */
		xDMATxDescriptors[ x ].CTRLSTAT = TDES_ENH_TCH | TDES_ENH_CIC( 3 ) | TDES_ENH_FS | TDES_ENH_LS | TDES_ENH_IC;
		xDMATxDescriptors[ x ].B2ADD = ( uint32_t ) &xDMATxDescriptors[ x + 1 ];
	}

	xDMATxDescriptors[ configNUM_TX_DESCRIPTORS - 1 ].CTRLSTAT |= TDES_ENH_TER;
	xDMATxDescriptors[ configNUM_TX_DESCRIPTORS - 1 ].B2ADD = ( uint32_t ) &xDMATxDescriptors[ 0 ];

	/* Point the DMA to the base of the descriptor list. */
	LPC_ETHERNET->DMA_TRANS_DES_ADDR = ( uint32_t ) xDMATxDescriptors;
}
/*-----------------------------------------------------------*/

static void prvSetupRxDescriptors( void )
{
BaseType_t x;
#if( ipconfigZERO_COPY_RX_DRIVER != 0 )
	NetworkBufferDescriptor_t *pxNetworkBuffer;
#endif

	/* Index to the next Rx descriptor to use. */
	ulNextRxDescriptorToProcess = 0;

	/* Clear RX descriptor list. */
	memset( ( void * )  xDMARxDescriptors, 0, sizeof( xDMARxDescriptors ) );

	for( x = 0; x < configNUM_RX_DESCRIPTORS; x++ )
	{
		/* Allocate a buffer of the largest	possible frame size as it is not
		known what size received frames will be. */

		#if( ipconfigZERO_COPY_RX_DRIVER != 0 )
		{
			pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( ipTOTAL_ETHERNET_FRAME_SIZE, 0 );

			/* During start-up there should be enough Network Buffers available,
			so it is safe to use configASSERT().
			In case this assert fails, please check: configNUM_RX_DESCRIPTORS,
			ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS, and in case BufferAllocation_2.c
			is included, check the amount of available heap. */
			configASSERT( pxNetworkBuffer != NULL );

			/* Pass the actual buffer to DMA. */
			xDMARxDescriptors[ x ].B1ADD = ( uint32_t ) pxNetworkBuffer->pucEthernetBuffer;
		}
		#else
		{
			/* All DMA descriptors are populated with permanent memory blocks.
			Their contents will be copy to Network Buffers. */
			xDMARxDescriptors[ x ].B1ADD = ( uint32_t ) pvPortMalloc( ipTOTAL_ETHERNET_FRAME_SIZE );
		}
		#endif /* ipconfigZERO_COPY_RX_DRIVER */

		/* Use an assert to check the allocation as +TCP applications will often
		not use a malloc failed hook as the TCP stack will recover from
		allocation failures. */
		configASSERT( xDMARxDescriptors[ x ].B1ADD );

		xDMARxDescriptors[ x ].B2ADD = ( uint32_t ) &( xDMARxDescriptors[ x + 1 ] );
		xDMARxDescriptors[ x ].CTRL = ( uint32_t ) RDES_ENH_BS1( ipTOTAL_ETHERNET_FRAME_SIZE ) | RDES_ENH_RCH;

		/* The descriptor is available for use by the DMA. */
		xDMARxDescriptors[ x ].STATUS = RDES_OWN;
	}

	/* RDES_ENH_RER  Receive End of Ring. */
	xDMARxDescriptors[ ( configNUM_RX_DESCRIPTORS - 1 ) ].CTRL |= RDES_ENH_RER;
	xDMARxDescriptors[ configNUM_RX_DESCRIPTORS - 1 ].B2ADD = ( uint32_t ) &( xDMARxDescriptors[ 0 ] );

	/* Point the DMA to the base of the descriptor list. */
	LPC_ETHERNET->DMA_REC_DES_ADDR = ( uint32_t ) xDMARxDescriptors;
}
/*-----------------------------------------------------------*/
configPLACE_IN_SECTION_RAM
static void prvRemoveTrailingBytes( NetworkBufferDescriptor_t *pxDescriptor )
{
size_t xExpectedLength;
IPPacket_t *pxIPPacket;

	pxIPPacket = ( IPPacket_t * ) pxDescriptor->pucEthernetBuffer;
	/* Look at the actual length of the packet, translate it to a host-endial notation. */
	xExpectedLength = sizeof( EthernetHeader_t ) + ( size_t ) FreeRTOS_htons( pxIPPacket->xIPHeader.usLength );

	if( xExpectedLength == ( pxDescriptor->xDataLength + 4 ) )
	{
		pxDescriptor->xDataLength -= 4;
	}
	else
	{
		if( pxDescriptor->xDataLength > xExpectedLength )
		{
			pxDescriptor->xDataLength = ( size_t ) xExpectedLength;
		}
	}
}
/*-----------------------------------------------------------*/
configPLACE_IN_SECTION_RAM
BaseType_t xGetPhyLinkStatus( void )
{
BaseType_t xReturn;

	if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) == 0 )
	{
		xReturn = pdFALSE;
	}
	else
	{
		xReturn = pdTRUE;
	}

	return xReturn;
}
/*-----------------------------------------------------------*/

configPLACE_IN_SECTION_RAM
static BaseType_t prvNetworkInterfaceInput()
{
BaseType_t xResult = pdFALSE;
uint32_t ulStatus;
eFrameProcessingResult_t eResult;
const TickType_t xDescriptorWaitTime = pdMS_TO_TICKS( 250 );
const UBaseType_t uxMinimumBuffersRemaining = 3UL;
uint16_t usLength;
NetworkBufferDescriptor_t *pxDescriptor;
#if( ipconfigZERO_COPY_RX_DRIVER != 0 )
	NetworkBufferDescriptor_t *pxNewDescriptor;
#endif /* ipconfigZERO_COPY_RX_DRIVER */
IPStackEvent_t xRxEvent = { eNetworkRxEvent, NULL };

	/* Process each descriptor that is not still in use by the DMA. */
	ulStatus = xDMARxDescriptors[ ulNextRxDescriptorToProcess ].STATUS;
	if( ( ulStatus & RDES_OWN ) == 0 )
	{
		/* Check packet for errors */
		if( ( ulStatus & nwRX_STATUS_ERROR_BITS ) != 0 )
		{
			/* There is some reception error. */
			/* Clear error bits. */
			ulStatus &= ~( ( uint32_t )nwRX_STATUS_ERROR_BITS );
		}
		else
		{
			xResult++;

			eResult = ipCONSIDER_FRAME_FOR_PROCESSING( ( const uint8_t * const ) ( xDMARxDescriptors[ ulNextRxDescriptorToProcess ].B1ADD ) );
			if( eResult == eProcessBuffer )
			{
				if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) == 0 )
				{
					ulPHYLinkStatus |= PHY_LINK_CONNECTED;
					FreeRTOS_printf( ( "prvEMACHandlerTask: PHY LS now %d (message received)\n", ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != 0 ) );
				}

			#if( ipconfigZERO_COPY_RX_DRIVER != 0 )
				if( uxGetNumberOfFreeNetworkBuffers() > uxMinimumBuffersRemaining )
				{
					pxNewDescriptor = pxGetNetworkBufferWithDescriptor( ipTOTAL_ETHERNET_FRAME_SIZE, xDescriptorWaitTime );
				}
				else
				{
					/* Too risky to allocate a new Network Buffer. */
					pxNewDescriptor = NULL;
				}
				if( pxNewDescriptor != NULL )
			#else
				if( uxGetNumberOfFreeNetworkBuffers() > uxMinimumBuffersRemaining )
			#endif /* ipconfigZERO_COPY_RX_DRIVER */
				{
			#if( ipconfigZERO_COPY_RX_DRIVER != 0 )
				const uint8_t *pucBuffer;
			#endif

					/* Get the actual length. */
					usLength = RDES_FLMSK( ulStatus );

					#if( ipconfigZERO_COPY_RX_DRIVER != 0 )
					{
						/* Replace the character buffer 'B1ADD'. */
						pucBuffer = ( const uint8_t * const ) ( xDMARxDescriptors[ ulNextRxDescriptorToProcess ].B1ADD );
						xDMARxDescriptors[ ulNextRxDescriptorToProcess ].B1ADD = ( uint32_t ) pxNewDescriptor->pucEthernetBuffer;

						/* 'B1ADD' contained the address of a 'pucEthernetBuffer' that
						belongs to a Network Buffer.  Find the original Network Buffer. */
						pxDescriptor = pxPacketBuffer_to_NetworkBuffer( pucBuffer );
						/* This zero-copy driver makes sure that every 'xDMARxDescriptors' contains
						a reference to a Network Buffer at any time.
						In case it runs out of Network Buffers, a DMA buffer won't be replaced,
						and the received messages is dropped. */
						configASSERT( pxDescriptor != NULL );
					}
					#else
					{
						/* Create a buffer of exactly the required length. */
						pxDescriptor = pxGetNetworkBufferWithDescriptor( usLength, xDescriptorWaitTime );
					}
					#endif /* ipconfigZERO_COPY_RX_DRIVER */

					if( pxDescriptor != NULL )
					{
						pxDescriptor->xDataLength = ( size_t ) usLength;
						#if( ipconfigZERO_COPY_RX_DRIVER == 0 )
						{
							/* Copy the data into the allocated buffer. */
							memcpy( ( void * ) pxDescriptor->pucEthernetBuffer, ( void * ) xDMARxDescriptors[ ulNextRxDescriptorToProcess ].B1ADD, usLength );
						}
						#endif /* ipconfigZERO_COPY_RX_DRIVER */
						/* It is possible that more data was copied than
						actually makes up the frame.  If this is the case
						adjust the length to remove any trailing bytes. */
						prvRemoveTrailingBytes( pxDescriptor );

						/* Pass the data to the TCP/IP task for processing. */
						xRxEvent.pvData = ( void * ) pxDescriptor;
						if( xSendEventStructToIPTask( &xRxEvent, xDescriptorWaitTime ) == pdFALSE )
						{
							/* Could not send the descriptor into the TCP/IP
							stack, it must be released. */
							vReleaseNetworkBufferAndDescriptor( pxDescriptor );
						}
						else
						{
							iptraceNETWORK_INTERFACE_RECEIVE();
						}
					}
				}
			}
			/* Got here because received data was sent to the IP task or the
			data contained an error and was discarded.  Give the descriptor
			back to the DMA. */
			xDMARxDescriptors[ ulNextRxDescriptorToProcess ].STATUS = ulStatus | RDES_OWN;

			/* Move onto the next descriptor. */
			ulNextRxDescriptorToProcess++;
			if( ulNextRxDescriptorToProcess >= configNUM_RX_DESCRIPTORS )
			{
				ulNextRxDescriptorToProcess = 0;
			}

			ulStatus = xDMARxDescriptors[ ulNextRxDescriptorToProcess ].STATUS;
		} /* if( ( ulStatus & nwRX_STATUS_ERROR_BITS ) != 0 ) */
	} /* if( ( ulStatus & RDES_OWN ) == 0 ) */

	/* Restart receive polling. */
	LPC_ETHERNET->DMA_REC_POLL_DEMAND = 1;

	return xResult;
}
/*-----------------------------------------------------------*/

configPLACE_IN_SECTION_RAM
void NETWORK_IRQHandler( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t ulDMAStatus;
const uint32_t ulRxInterruptMask =
	DMA_ST_RI |		/* Receive interrupt */
	DMA_ST_RU;		/* Receive buffer unavailable */
const uint32_t ulTxInterruptMask =
	DMA_ST_TI |		/* Transmit interrupt */
	DMA_ST_TPS;		/* Transmit process stopped */

	configASSERT( xRxHanderTask );

	/* Get pending interrupts. */
	ulDMAStatus = LPC_ETHERNET->DMA_STAT;

	/* RX group interrupt(s). */
	if( ( ulDMAStatus & ulRxInterruptMask ) != 0x00 )
	{
		/* Remember that an RX event has happened. */
		ulISREvents |= EMAC_IF_RX_EVENT;
		vTaskNotifyGiveFromISR( xRxHanderTask, &xHigherPriorityTaskWoken );
	}

	/* TX group interrupt(s). */
	if( ( ulDMAStatus & ulTxInterruptMask ) != 0x00 )
	{
		/* Remember that a TX event has happened. */
		ulISREvents |= EMAC_IF_TX_EVENT;
		vTaskNotifyGiveFromISR( xRxHanderTask, &xHigherPriorityTaskWoken );
	}

	/* Test for 'Abnormal interrupt summary'. */
	if( ( ulDMAStatus & DMA_ST_AIE ) != 0x00 )
	{
		/* The trace macro must be written such that it can be called from
		an interrupt. */
		iptraceETHERNET_RX_EVENT_LOST();
	}

	/* Clear pending interrupts */
	LPC_ETHERNET->DMA_STAT = ulDMAStatus;

	/* Context switch needed? */
	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

static BaseType_t prvSetLinkSpeed( void )
{
BaseType_t xReturn = pdFAIL;
TickType_t xTimeOnEntering;
uint32_t ulPhyStatus;
const TickType_t xAutoNegotiateDelay = pdMS_TO_TICKS( 5000UL );

	/* Ensure polling does not starve lower priority tasks by temporarily
	setting the priority of this task to that of the idle task. */
	vTaskPrioritySet( NULL, tskIDLE_PRIORITY );

	xTimeOnEntering = xTaskGetTickCount();
	do
	{
		ulPhyStatus = lpcPHYStsPoll();
		if( ( ulPhyStatus & PHY_LINK_CONNECTED ) != 0x00 )
		{
			/* Set interface speed and duplex. */
			if( ( ulPhyStatus & PHY_LINK_SPEED100 ) != 0x00 )
			{
				Chip_ENET_SetSpeed( LPC_ETHERNET, 1 );
			}
			else
			{
				Chip_ENET_SetSpeed( LPC_ETHERNET, 0 );
			}

			if( ( ulPhyStatus & PHY_LINK_FULLDUPLX ) != 0x00 )
			{
				Chip_ENET_SetDuplex( LPC_ETHERNET, pdTRUE );
			}
			else
			{
				Chip_ENET_SetDuplex( LPC_ETHERNET, pdFALSE );
			}

			xReturn = pdPASS;
			break;
		}
	} while( ( xTaskGetTickCount() - xTimeOnEntering ) < xAutoNegotiateDelay );

	/* Reset the priority of this task back to its original value. */
	vTaskPrioritySet( NULL, ipconfigIP_TASK_PRIORITY );

	return xReturn;
}
/*-----------------------------------------------------------*/

static uint32_t prvGenerateCRC32( const uint8_t *ucAddress )
{
unsigned int j;
const uint32_t Polynomial = 0xEDB88320;
uint32_t crc = ~0ul;
const uint8_t *pucCurrent = ( const uint8_t * ) ucAddress;
const uint8_t *pucLast = pucCurrent + 6;

    /* Calculate  normal CRC32 */
    while( pucCurrent < pucLast )
    {
        crc ^= *( pucCurrent++ );
        for( j = 0; j < 8; j++ )
        {
            if( ( crc & 1 ) != 0 )
            {
                crc = (crc >> 1) ^ Polynomial;
            }
            else
            {
                crc >>= 1;
            }
        }
    }
    return ~crc;
}
/*-----------------------------------------------------------*/

static uint32_t prvGetHashIndex( const uint8_t *ucAddress )
{
uint32_t ulCrc = prvGenerateCRC32( ucAddress );
uint32_t ulIndex = 0ul;
BaseType_t xCount = 6;

    /* Take the lowest 6 bits of the CRC32 and reverse them */
    while( xCount-- )
    {
        ulIndex <<= 1;
        ulIndex |= ( ulCrc & 1 );
        ulCrc >>= 1;
    }

    /* This is the has value of 'ucAddress' */
    return ulIndex;
}
/*-----------------------------------------------------------*/

static void prvAddMACAddress( const uint8_t* ucMacAddress )
{
BaseType_t xIndex;

    xIndex = prvGetHashIndex( ucMacAddress );
    if( xIndex >= 32 )
    {
        LPC_ETHERNET->MAC_HASHTABLE_HIGH |= ( 1u << ( xIndex - 32 ) );
    }
    else
    {
        LPC_ETHERNET->MAC_HASHTABLE_LOW |= ( 1u << xIndex );
    }
}
/*-----------------------------------------------------------*/

configPLACE_IN_SECTION_RAM
static void prvEMACHandlerTask( void *pvParameters )
{
TimeOut_t xPhyTime;
TickType_t xPhyRemTime;
UBaseType_t uxLastMinBufferCount = 0;
UBaseType_t uxCurrentCount;
BaseType_t xResult = 0;
uint32_t ulStatus;
const TickType_t xBlockTime = pdMS_TO_TICKS( 5000ul );

	/* Remove compiler warning about unused parameter. */
	( void ) pvParameters;

	/* A possibility to set some additional task properties. */
	iptraceEMAC_TASK_STARTING();

	vTaskSetTimeOutState( &xPhyTime );
	xPhyRemTime = pdMS_TO_TICKS( PHY_LS_LOW_CHECK_TIME_MS );

	for( ;; )
	{
		uxCurrentCount = uxGetMinimumFreeNetworkBuffers();
		if( uxLastMinBufferCount != uxCurrentCount )
		{
			/* The logging produced below may be helpful
			while tuning +TCP: see how many buffers are in use. */
			uxLastMinBufferCount = uxCurrentCount;
			FreeRTOS_printf( ( "Network buffers: %lu lowest %lu\n",
				uxGetNumberOfFreeNetworkBuffers(), uxCurrentCount ) );
		}

		#if( ipconfigCHECK_IP_QUEUE_SPACE != 0 )
		{
		static UBaseType_t uxLastMinQueueSpace = 0;

			uxCurrentCount = uxGetMinimumIPQueueSpace();
			if( uxLastMinQueueSpace != uxCurrentCount )
			{
				/* The logging produced below may be helpful
				while tuning +TCP: see how many buffers are in use. */
				uxLastMinQueueSpace = uxCurrentCount;
				FreeRTOS_printf( ( "Queue space: lowest %lu\n", uxCurrentCount ) );
			}
		}
		#endif /* ipconfigCHECK_IP_QUEUE_SPACE */

		ulTaskNotifyTake( pdTRUE, xBlockTime );

		xResult = ( BaseType_t ) 0;

		if( ( ulISREvents & EMAC_IF_TX_EVENT ) != 0 )
		{
			/* Code to release TX buffers if zero-copy is used. */
			ulISREvents &= ~EMAC_IF_TX_EVENT;
			{
				/* Check if DMA packets have been delivered. */
				vClearTXBuffers();
			}
		}

		if( ( ulISREvents & EMAC_IF_RX_EVENT ) != 0 )
		{
			ulISREvents &= ~EMAC_IF_RX_EVENT;

			xResult = prvNetworkInterfaceInput();
			if( xResult > 0 )
			{
			  	while( prvNetworkInterfaceInput() > 0 )
				{
				}
			}
		}

		if( xResult > 0 )
		{
			/* A packet was received. No need to check for the PHY status now,
			but set a timer to check it later on. */
			vTaskSetTimeOutState( &xPhyTime );
			xPhyRemTime = pdMS_TO_TICKS( PHY_LS_HIGH_CHECK_TIME_MS );
			xResult = 0;
		}
		else if( xTaskCheckForTimeOut( &xPhyTime, &xPhyRemTime ) != pdFALSE )
		{
			ulStatus = lpcPHYStsPoll();

			if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != ( ulStatus & PHY_LINK_CONNECTED ) )
			{
				ulPHYLinkStatus = ulStatus;
				FreeRTOS_printf( ( "prvEMACHandlerTask: PHY LS now %d (polled PHY)\n", ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != 0 ) );
			}

			vTaskSetTimeOutState( &xPhyTime );
			if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != 0 )
			{
				xPhyRemTime = pdMS_TO_TICKS( PHY_LS_HIGH_CHECK_TIME_MS );
			}
			else
			{
				xPhyRemTime = pdMS_TO_TICKS( PHY_LS_LOW_CHECK_TIME_MS );
			}
		}
	}
}
/*-----------------------------------------------------------*/
